//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _PLAYER_PARTICLESYSTEM_H
#define _PLAYER_PARTICLESYSTEM_H
//
#include <global.h>
#include <vector>
#include <stack>
#include <deque>
#include <osg/Drawable>
#include <model/particletemplate.h>
#include "particle.h"
#include "particleparam.h"
#include "controller_fwd.h"
#include "particlefactory_fwd.h"
#ifdef OSGPARTICLE_USE_ReadWriteMutex
#include <OpenThreads/ReadWriteMutex>
#else
#include <OpenThreads/Mutex>
#include <OpenThreads/ScopedLock>
#endif
//
namespace player
{
class ParticleSystem:public osg::Drawable
{
public:
   enum Alignment
   {
      alignment_billboard,
      alignment_fixed
   };
   enum ParticleScaleReferenceFrame
   {
      particle_scale_local_coordinates,
      particle_scale_world_coordinates
   };
#ifdef OSGPARTICLE_USE_ReadWriteMutex
   typedef OpenThreads::ReadWriteMutex ReadWriterMutex;
   typedef OpenThreads::ScopedReadLock ScopedReadLock;
   typedef OpenThreads::ScopedWriteLock ScopedWriteLock;
#else
   typedef OpenThreads::Mutex ReadWriterMutex;
   typedef OpenThreads::ScopedLock<OpenThreads::Mutex> ScopedReadLock;
   typedef OpenThreads::ScopedLock<OpenThreads::Mutex> ScopedWriteLock;
#endif
   META_Object(fwc,ParticleSystem);
   //
   ParticleSystem();
   ParticleSystem(const ParticleSystem &p_copy,const osg::CopyOp &p_copyop=osg::CopyOp::SHALLOW_COPY);
   void assign_controller(Controller *p_controller,ParticleFactory *p_particle_factory,const model::ParticleTemplate *p_particle_template);
   void deassign_controller();
   //void update_from_controller();
   void update_from_particle_template();
   //
   inline Controller *get_controller()const;
   inline ParticleFactory *get_particle_factory()const;
   //
   inline bool get_particles_visible()const;
   inline bool get_gravitation_enabled()const;
   void update_gravitation_enabled();
   inline bool get_friction_enabled()const;
   void update_friction_enabled();
   inline model::ParticleTemplate::Shape get_particle_shape()const;
   inline float get_particle_density()const;
   inline int get_particle_tile_s()const;
   inline int get_particle_tile_t()const;
   inline float get_particle_s_tile()const;
   inline float get_particle_t_tile()const;
   inline float get_particle_num_tiles()const;
   inline model::ParticleTemplate::TileSpeedMode get_particle_tile_speed_mode()const;
   inline Alignment get_particle_alignment()const;
   inline const osg::Vec3 &get_align_vector_x()const;
   inline const osg::Vec3 &get_align_vector_y()const;
   inline ParticleScaleReferenceFrame get_particle_scale_reference_frame()const;
   inline const osg::BoundingBox &get_default_bounding_box()const;
   inline bool get_double_pass_rendering()const;
   //
   inline int get_last_frame_number()const;
   inline ReadWriterMutex *get_read_write_mutex()const;
   virtual void update(double p_dt);
   virtual void reset();
   inline void schedule_create(const ParticleParam &p_param);
protected:
   ParticleSystem &operator=(const ParticleSystem&);
   //
   void set_particle_texture_tile(int p_tile_s,int p_tile_t,float p_num_tiles);
   void set_default_attributes(const std::string &p_texturefile,bool p_emissive_particles,bool p_lighting,int p_texture_unit);
   //
   int new_particle();
   inline void destroy_particle(int p_i);
   inline void reuse_particle(int p_i);
   //
   inline void update_bounds(const osg::Vec3 &p_position,float p_radius);
   void single_pass_render(osg::State &p_state,const osg::Matrix &p_modelview)const;
//virtuals
public:
   virtual ~ParticleSystem();
   virtual void drawImplementation(osg::RenderInfo& renderInfo) const;
   virtual osg::BoundingBox computeBound() const;
//data
protected:
   typedef std::vector<Particle> ParticleVector;
   typedef std::stack<int> DeathStack;
   typedef std::deque<ParticleParam> CreateQueue;
   //
   Controller *controller;
   ParticleFactory *particle_factory;
   ParticleVector particles;
   DeathStack dead_particles;
   CreateQueue create_queue;
   Particle *particle_template;
   const model::ParticleTemplate *model_particle_template;
   //
   bool particles_visible;
   bool gravitation_enabled;
   bool friction_enabled;
   model::ParticleTemplate::Shape particle_shape;
   float particle_density;
   int particle_tile_s;
   int particle_tile_t;
   float particle_s_tile;
   float particle_t_tile;
   float particle_num_tile;
   model::ParticleTemplate::TileSpeedMode particle_tile_speed_mode;
   Alignment alignment;
   osg::Vec3 align_x_axis;
   osg::Vec3 align_y_axis;
   ParticleScaleReferenceFrame particle_scale_reference_frame;
   osg::BoundingBox default_bounding_box;
   bool doublepass;
   //
   osg::Vec3 bounds_min;
   osg::Vec3 bounds_max;
   bool reset_bounds_flag;
   bool bounds_computed;
   mutable int last_frame;
   mutable int draw_count;
   mutable ReadWriterMutex read_write_mutex;
};
// inline functions
inline Controller *ParticleSystem::get_controller()const
{
   return controller;
}
inline ParticleFactory *ParticleSystem::get_particle_factory()const
{
   return particle_factory;
}
//
inline bool ParticleSystem::get_particles_visible()const
{
   return particles_visible;
}
inline bool ParticleSystem::get_gravitation_enabled()const
{
   return gravitation_enabled;
}
inline bool ParticleSystem::get_friction_enabled()const
{
   return friction_enabled;
}
inline model::ParticleTemplate::Shape ParticleSystem::get_particle_shape()const
{
   return particle_shape;
}
inline float ParticleSystem::get_particle_density()const
{
   return particle_density;
}
inline int ParticleSystem::get_particle_tile_s()const
{
   return particle_tile_s;
}
inline int ParticleSystem::get_particle_tile_t()const
{
   return particle_tile_t;
}
inline float ParticleSystem::get_particle_s_tile()const
{
   return particle_s_tile;
}
inline float ParticleSystem::get_particle_t_tile()const
{
   return particle_t_tile;
}
inline float ParticleSystem::get_particle_num_tiles()const
{
   return particle_num_tile;
}
inline model::ParticleTemplate::TileSpeedMode ParticleSystem::get_particle_tile_speed_mode()const
{
   return particle_tile_speed_mode;
}
inline ParticleSystem::Alignment ParticleSystem::get_particle_alignment()const
{
   return alignment;
}
inline const osg::Vec3 &ParticleSystem::get_align_vector_x()const
{
   return align_x_axis;
}
inline const osg::Vec3 &ParticleSystem::get_align_vector_y()const
{
   return align_y_axis;
}
inline ParticleSystem::ParticleScaleReferenceFrame ParticleSystem::get_particle_scale_reference_frame()const
{
   return particle_scale_reference_frame;
}
inline const osg::BoundingBox &ParticleSystem::get_default_bounding_box()const
{
   return default_bounding_box;
}
inline bool ParticleSystem::get_double_pass_rendering()const
{
   return doublepass;
}
//
inline int ParticleSystem::get_last_frame_number()const
{
   return last_frame;
}
inline ParticleSystem::ReadWriterMutex *ParticleSystem::get_read_write_mutex()const
{
   return &read_write_mutex;
}
inline void ParticleSystem::schedule_create(const ParticleParam &p_param)
{
   create_queue.push_back(p_param);
}
//
inline void ParticleSystem::destroy_particle(int p_i)
{
   particles[p_i].kill();
}
inline void ParticleSystem::reuse_particle(int p_i)
{
   dead_particles.push(p_i);
}
inline void ParticleSystem::update_bounds(const osg::Vec3 &p_position,float p_radius)
{
   if(reset_bounds_flag)
   {
      reset_bounds_flag=false;
      bounds_min=p_position-osg::Vec3(p_radius,p_radius,p_radius);
      bounds_max=p_position+osg::Vec3(p_radius,p_radius,p_radius);
   }
   else
   {
      if(p_position.x()-p_radius<bounds_min.x()) bounds_min.x()=p_position.x()-p_radius;
      if(p_position.y()-p_radius<bounds_min.y()) bounds_min.y()=p_position.y()-p_radius;
      if(p_position.z()-p_radius<bounds_min.z()) bounds_min.z()=p_position.z()-p_radius;
      if(p_position.x()+p_radius>bounds_max.x()) bounds_max.x()=p_position.x()+p_radius;
      if(p_position.y()+p_radius>bounds_max.y()) bounds_max.y()=p_position.y()+p_radius;
      if(p_position.z()+p_radius>bounds_max.z()) bounds_max.z()=p_position.z()+p_radius;
   }
   if(!bounds_computed)
      bounds_computed=true;
}
}
//
#endif
